#include "userprog/syscall.h"
#include <stdio.h>
#include <syscall-nr.h>
#include "threads/interrupt.h"
#include "threads/thread.h"
#include "threads/init.h"
#include "threads/vaddr.h"
#include "filesys/filesys.h"
#include "filesys/file.h"
#include "threads/malloc.h"
#include "devices/input.h"
#include "lib/kernel/console.h"
#include "devices/shutdown.h"
#include "lib/stdio.h"
#include "userprog/process.h"
#include "userprog/pagedir.h"
#include <string.h>
#include "threads/synch.h"
#include "threads/vaddr.h"
#include "userprog/pagedir.h"
#include "vm/frame.h"
#include "vm/swap.h"
#include "filesys/inode.h"

struct file* getFile(int fd);
void* getArg1(struct intr_frame* f);
void* getArg2(struct intr_frame* f);
void* getArg3(struct intr_frame* f);
void halt(void);

int exec (const char *cmd_line);
int wait(int pid);
bool create (const char *file, unsigned initial_size);
bool remove (const char *file);
int open (const char *file);
int filesize (int fd);
int read (int fd, void *buffer, unsigned size);
int write (int fd,void* buffer,unsigned size);
void seek (int fd, unsigned position);
unsigned tell (int fd);
void close (int fd);
mapid_t mmap (int fd, void *addr);
void munmap (mapid_t mapping);
bool mkdir(const char *dir);
bool chdir(const char *dir);
bool isdir (int fd);
bool readdir (int fd, char *name);
int inumber(int fd);

static void syscall_handler (struct intr_frame *);
//ht
//全局变量，记录mapid
static mapid_t allmid = 1;


void
syscall_init (void) 
{
  intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall");
}

static void
syscall_handler (struct intr_frame *f)
{
    if(f->esp == NULL || !is_in_vaddr(thread_current()->pagedir,f->esp)
            || !is_user_vaddr(f->esp)){
        exit(-1);
    }
    int sysnum = *(int*)(f->esp);

    thread_current()->esp = f->esp;

    switch(sysnum){
    case SYS_HALT:
        halt();
        break;
    case SYS_EXIT:
        exit(*(int*)getArg1(f));
        break;
    case SYS_EXEC:
        f->eax = exec(*(char**)getArg1(f));
        break;
    case SYS_WAIT:
        f->eax = wait(*(int*)getArg1(f));
        break;
    case SYS_CREATE:
        f->eax = create(*(char**)getArg1(f),*(unsigned*)getArg2(f));
        break;
    case SYS_REMOVE:
        f->eax = remove(*(char**)getArg1(f));
        break;
    case SYS_OPEN:
        f->eax = open(*(char**)getArg1(f));
        break;
    case SYS_FILESIZE:
        f->eax = filesize(*(int*)getArg1(f));
        break;
    case SYS_READ:
        f->eax = read(*(int*)getArg1(f),*(char**)getArg2(f),*(unsigned*)getArg3(f));
        break;
    case SYS_WRITE:
        f->eax = write(*(int*)getArg1(f),*(char**)getArg2(f),*(unsigned*)getArg3(f));
        break;
    case SYS_SEEK:
        seek(*(int*)getArg1(f),*(unsigned*)getArg2(f));
        break;
    case SYS_TELL:
        f->eax = tell(*(int*)getArg1(f));
        break;
    case SYS_CLOSE:
        close(*(int*)getArg1(f));
        break;
    case SYS_MMAP://ht
        f->eax = mmap(*(int*)getArg1(f),*(char **)getArg2(f));
        break;
    case SYS_MUNMAP:
        munmap(*(mapid_t*)getArg1(f));
        break;
    case SYS_MKDIR:
        f->eax = mkdir(*(char **)getArg1(f));
        break;
    case SYS_CHDIR:
        f->eax = chdir(*(char **)getArg1(f));
        break;
    case SYS_ISDIR:
        f->eax = isdir(*(int *)getArg1(f));
        break;
    case SYS_READDIR:
        f->eax = readdir(*(int *)getArg1(f),*(char **)getArg2(f));
        break;
    case SYS_INUMBER:
        f->eax = inumber(*(int *)getArg1(f));
        break;
    default:
        exit (-1);
        break;
    }
  //printf ("system call!\n");
  //thread_exit ();
}

struct file* getFile(int fd)
{
	struct thread* cur = thread_current();
	struct list_elem *e;
	for (e = list_begin (&cur->file_list); e != list_end (&cur->file_list);
		 e = list_next (e))
	{
		struct open_file* of = list_entry (e, struct open_file, fd_elem);

        if(of->fd == fd && !of->is_dir){
            return of->ptr.fp;
		}
	}
	return NULL;
}

void* getArg1(struct intr_frame* f)
{
	if(!is_user_vaddr(f->esp + 4)){
		exit(-1);
	}
	return (f->esp + 4);
}

void* getArg2(struct intr_frame* f)
{
	if(!is_user_vaddr(f->esp + 8)){
		exit(-1);
	}
	return (f->esp + 8);
}

void* getArg3(struct intr_frame* f)
{
	if(!is_user_vaddr(f->esp + 12)){
		exit(-1);
	}
	return (f->esp + 12);
}

void halt()
{
	shutdown();
}
void exit(int status)
{
	thread_current()->return_status = status;
	printf ("%s: exit(%d)\n",thread_current()->name,status);
	thread_exit ();
}

int exec (const char *cmd_line)
{

	if(cmd_line == NULL || !is_user_vaddr(cmd_line)
			|| !is_in_vaddr(thread_current()->pagedir,cmd_line)){
		exit(-1);
	}

	return process_execute(cmd_line);
}

int wait(int pid)
{
	return process_wait(pid);
}

bool create (const char *file, unsigned initial_size)
{
    if (!strcmp(file,"file968"))
        printf("1\n");
	if(file == NULL || !is_in_vaddr(thread_current()->pagedir,file)){
		exit(-1);
	}
	return filesys_create (file, initial_size);
}

bool remove (const char *file)
{
	if(file == NULL || !is_in_vaddr(thread_current()->pagedir,file)){
		exit(-1);
	}
	return filesys_remove (file);
}

int open (const char *file_name)
{
    if (!strcmp(file_name,"file968"))
        printf("1\n");

	if(!is_in_vaddr(thread_current()->pagedir,file_name)){
		exit(-1);
	}
	if(file_name == NULL || strcmp(file_name,"") == 0){
		return -1;
	}
    struct dir * di = NULL;
    struct file* fi = filesys_open(file_name,&di);

    //printf("fi%p:  di:%p\n",fi,di);
    if (fi == NULL && di == NULL)
        return -1;

	struct open_file* of = (struct open_file*)malloc(sizeof(struct open_file));
	struct thread* cur = thread_current();

	if(list_empty(&cur->file_list)){
		of->fd = 2;
	}else{
		of->fd = list_entry(list_back(&cur->file_list),struct open_file,fd_elem)->fd + 1;
	}

    if (fi != NULL)
    {
        of->ptr.fp = fi;
        of->is_dir = false;
    }
    else
    {
        of->ptr.dp = di;
        of->is_dir = true;
    }
	list_push_back(&cur->file_list,&of->fd_elem);

	return of->fd;
}

int filesize (int fd)
{
	struct file* fi = getFile(fd);
	if(fi == NULL){
		exit(-1);
	}else{
		return file_length(fi);
	}
}

int read (int fd, void *buffer, unsigned size)
{
	if(buffer == NULL || !is_user_vaddr(buffer)
            || !is_in_vaddr(thread_current()->pagedir,buffer))
    {
		exit(-1);
	}
    if(fd == 0)
    {
		return input_getc();
    }
    else
    {
		struct file* fi = getFile(fd);

        if(fi == NULL)
        {
			return -1;
        }else{
        	//为了swap,这里要分页读

            uint32_t bytes_read = 0;	//一共读了bytes_read字节
            uint32_t read = 0;			//每次读的字节
            uint32_t new_size = 0;
            new_size = (unsigned)(pg_round_up(buffer) - buffer);	//第一次读的字节
            if(size < new_size){
            	return file_read(fi,buffer,size);		//不够一页，直接读
            }
            while(true){
                force_pin(buffer);			//让它page_fault,并swap，并钉住，这时它不能被换出
                read = file_read(fi,buffer,new_size);	//这时候可以读了
                bytes_read += read;
                unpin_page(pg_no(buffer));		//解除钉住，这时它可以被换出
                if(read == 0){
                	sema_up(&thread_current()->parent_sema);
                	sema_down(&(thread_current()->parent->rw_sema));
                }
                if(read != new_size){		//如果读的和要读的大小不相等
                	return bytes_read;
                }
                buffer += read;			//下一页
                size -= read;
                if(size <= 0){		//已经读完
                    break;
                }
                if(size >= PGSIZE){
                	new_size = PGSIZE;
                }else{
                	new_size = size;		//这时是最后一页
                }
            }
            return bytes_read;
        }
	}
}

int write (int fd,void* buffer,unsigned size)
{
    //asm volatile ("movl %0,%%esp"::"a"(f->esp));
	if(buffer == NULL || !is_user_vaddr(buffer)
			|| !is_in_vaddr(thread_current()->pagedir,buffer)){
		exit(-1);
	}
	if(fd == STDOUT_FILENO){
		putbuf(buffer,size);
	    return size;
	}else{
		struct file* fi = getFile(fd);
		if(fi == NULL){
			exit(-1);
		}else{
			//这里要分页写，代码和read几乎一样

			uint32_t bytes_write = 0;
			uint32_t write = 0;
			uint32_t new_size = 0;

			new_size = (unsigned)(pg_round_up(buffer) - buffer);
			if(size < new_size){
				return file_write(fi,buffer,size);
			}
			while(true){
				force_pin(buffer);
				write = file_write(fi,buffer,new_size);
				bytes_write += write;
				unpin_page(pg_no(buffer));
				if(write != new_size){
					return bytes_write;
				}
				buffer += write;
				size -= write;
				if(size <= 0){
					break;
				}
				if(size >= PGSIZE){
					new_size = PGSIZE;
				}else{
					new_size = size;
				}
			}
			return bytes_write;
		}
	}

}

void seek (int fd, unsigned position)
{
	struct file* fi = getFile(fd);
	if(fi == NULL){
		exit(-1);
	}else{
		file_seek(fi,position);
	}
}

unsigned tell (int fd)
{
	struct file* fi = getFile(fd);
	if(fi == NULL){
		exit(-1);
	}else{
		return file_tell(fi);
	}
}

void close (int fd)
{
    struct list_elem* e;
    struct thread* cur = thread_current();
    for(e = list_begin(&cur->file_list); e != list_end(&cur->file_list); e = list_next(e))
    {
        struct open_file* of;
        of = list_entry(e,struct open_file,fd_elem);
        if(of->fd == fd)
        {
            if (of->is_dir)
                dir_close(of->ptr.dp);
            else
                file_close(of->ptr.fp);
            list_remove(e);
            free(of);
            return;
        }
    }
    exit(-1);
}


//ht
mapid_t mmap (int fd, void *addr)
{
//    struct list_elem *e;
    struct list_elem *fe;
    struct thread *cur = thread_current();
//    struct mapid *m = NULL;
    struct open_file *of = NULL;
    int length = filesize(fd);
    int offset = 0;

    //0长度文件
    if(length<=0)
        return -1;
    //地址非法
    if(addr == NULL || addr == 0x0 || pg_ofs(addr)!=0)
        return -1;
    //fd非法
    if(fd == 0 || fd == 1)
        return -1;

    //遍历file表，找出fd对应的文件指针
    for (fe = list_begin (&cur->file_list); fe != list_end (&cur->file_list);
         fe = list_next (fe))
      {
        of  = list_entry (fe, struct open_file,fd_elem);
        if(of->fd == fd)
        {
            break;
        }
      }
    //如果没找到，返回
    if(fe == list_end(&cur->file_list))
    {
        return -1;
     }

    //将fd一页一页映射到虚存
    while(offset < (length-PGSIZE))
    {

        //如果在SPT里面已经有了这一页，则返回
        if(SPTE_find(&cur->SPT,pg_no (addr+offset))!=NULL)
        {
            return -1;
        }

        void *kpage = falloc_get_frame(0);
        if(kpage == NULL)
        {
            if(!swap_replace(pg_no(addr+offset),true))
            {
                //printf("c\n");
                exit(-1);
            }
        }
        else
        {
//        printf("%p\n",kpage);
            regist_frame(pg_no(kpage),pg_no(addr+offset));
            page_map(&cur->SPT,pg_no(addr+offset),pg_no(kpage),true);
        }

        file_read_at(of->ptr.fp,addr+offset,PGSIZE,offset);
        pagedir_set_dirty(cur->pagedir,addr+offset,false);

        //添加一项mmp_page项
        struct mmp_page *mmp =  (struct mmp_page *)malloc(sizeof(struct mmp_page));
        mmp->addr=addr+offset;
        mmp->offset=offset;
        mmp->fileptr=of->ptr.fp;
        mmp->ready_bytes=PGSIZE;
        mmp->mid=allmid;
        list_push_back(&cur->mmp_list,&mmp->elem);

        offset += PGSIZE;
    }

    //处理最后一页
    if(SPTE_find(&cur->SPT,pg_no (addr+offset))!=NULL)
    {
        return -1;
    }

    void *kpage = falloc_get_frame(0);
    if(kpage == NULL)
    {
        if(!swap_replace(pg_no(addr+offset),true))
        {
            //printf("c\n");
            exit(-1);
        }
    }
    else
    {
//    printf("%p\n",kpage);
         regist_frame(pg_no(kpage),pg_no(addr+offset));
         page_map(&cur->SPT,pg_no(addr+offset),pg_no(kpage),true);
    }

    file_read_at(of->ptr.fp,addr+offset,PGSIZE,offset);
    pagedir_set_dirty(cur->pagedir,addr+offset,false);

    //添加一项mapid表项
    struct mmp_page *mmp =  (struct mmp_page *)malloc(sizeof(struct mmp_page));
    mmp->addr=addr+offset;
    mmp->offset=offset;
    mmp->fileptr=of->ptr.fp;
    mmp->ready_bytes=length-offset;
    mmp->mid=allmid;
    list_push_back(&cur->mmp_list,&mmp->elem);


    mapid_t mapid=allmid;
    allmid++;
    return mapid;
}

void munmap (mapid_t mapping)
{
    struct list_elem *e;
//    struct list_elem *fe;
    struct thread *cur = thread_current();
    struct mmp_page *m=NULL;

    struct SPTE *s;
    //在mapid表中寻找mapid对应的项
    for (e = list_begin (&cur->mmp_list); e != list_end (&cur->mmp_list);
         e = list_next (e))
      {
        m= list_entry (e, struct mmp_page, elem);
        if(m->mid == mapping)
        {
            s=SPTE_find(&cur->SPT,pg_no(m->addr));

            if(pagedir_is_dirty(cur->pagedir,m->addr))
            {
                //如果该页已经被修改，则要写回文件中
                file_write_at(m->fileptr,m->addr, m->ready_bytes,m->offset);
            }

            page_unmap(cur,s->page_number);
            list_remove(&s->elem);
            list_remove(&m->elem);
        }
      }
}

bool chdir (const char *dir)
{
    return filesys_chdir(dir);
}

bool mkdir (const char *dir)
{
    return filesys_mkdir(dir);
}

bool readdir (int fd, char *name)
{
    struct thread* cur = thread_current();
    struct list_elem *e;
    for (e = list_begin (&cur->file_list); e != list_end (&cur->file_list); e = list_next (e))
    {
        struct open_file* of = list_entry (e, struct open_file, fd_elem);

        if(of->fd == fd)
        {
            if (of->is_dir)
            {
                return dir_readdir(of->ptr.dp,name);
            }
            else
                return false;
        }
    }
    return false;
}

bool isdir (int fd)
{
    struct thread* cur = thread_current();
    struct list_elem *e;
    for (e = list_begin (&cur->file_list); e != list_end (&cur->file_list); e = list_next (e))
    {
        struct open_file* of = list_entry (e, struct open_file, fd_elem);

        if(of->fd == fd)
        {
            if (of->is_dir)
                return true;
            else
                return false;
        }
    }
    return false;
}

int inumber (int fd)
{
    struct thread* cur = thread_current();
    struct list_elem *e;
    for (e = list_begin (&cur->file_list); e != list_end (&cur->file_list); e = list_next (e))
    {
        struct open_file* of;
        of = list_entry (e, struct open_file, fd_elem);

        if(of->fd == fd)
        {
            if (of->is_dir)
                return inode_get_inumber(dir_get_inode(of->ptr.dp));
            else
                return inode_get_inumber(file_get_inode(of->ptr.fp));
        }
    }
    return -1;
}

